iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 24
1
Mobile Development

Flutter---Google推出的跨平台框架,Android、iOS一起搞定系列 第 24

【Flutter基礎概念與實作】 Day24–設計電影細節頁面、播放Youtube影片

  • 分享至 

  • xImage
  •  

今天的目標很簡單,就是把顯示電影細節的頁面完成。

Movie Detail Page

在home資料夾下新增「movie_detail_page.dart」
首先先設定建構子,傳入要顯示的電影資訊,並初始化YoutubeRepository和YoutubeBloc(要記得引入youtube.dart)。

class MovieDetailPage extends StatefulWidget {
  final posterPath;
  final overview;
  final releaseDate;
  final title;
  final voteAverage;
  final movieId;

  MovieDetailPage(
      {Key key,
      this.posterPath,
      this.overview,
      this.releaseDate,
      this.title,
      this.voteAverage,
      this.movieId})
      : super(key: key);
  @override
  _MovieDetailPageState createState() => _MovieDetailPageState();
}

class _MovieDetailPageState extends State<MovieDetailPage> {
  String get posterPath => widget.posterPath;
  String get overview => widget.overview;
  String get releaseDate => widget.releaseDate;
  String get title => widget.title;
  String get voteAverage => widget.voteAverage.toString();
  String get movieId => widget.movieId.toString();
  
  bool isOverviewSelected = false;
  YoutubeBloc _youtubeBloc;
  YoutubeRepository _youtubeRepository;

  @override
  void initState() {
    _youtubeRepository = YoutubeRepository();
    _youtubeBloc = YoutubeBloc(youtubeRepository: _youtubeRepository);
    _youtubeBloc.dispatch(SearchYoutubeEvent("$title 預告片"));

    super.initState();
  }
}

接下來是UI的程式碼
做到了以下幾個功能:

  • 使用SliverAppBar讓AppBar在向下滑動的時候會被隱藏起來
  • 為了讓整個頁面可以被滑動所以用SliverList包覆起來
  • GestureDetector讓使用者可以縮放大綱文字
  • 排版電影標題、分數、上映日期
  • BlocBuilder監聽YoutubeBloc狀態,顯示搜尋到的預告片
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
          top: false,
          bottom: false,
          child: CustomScrollView(slivers: <Widget>[
            SliverAppBar(
              expandedHeight: 200.0,
              floating: false,
              pinned: false,
              elevation: 0.0,
              flexibleSpace: FlexibleSpaceBar(
                  background: Image.network(
                "https://image.tmdb.org/t/p/w500${posterPath}",
                fit: BoxFit.cover,
              )),
            ),
            SliverList(
              delegate: SliverChildListDelegate(
                [
                  Container(margin: EdgeInsets.only(top: 5.0)),
                  Text(
                    title,
                    style: TextStyle(
                      fontSize: 25.0,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  Container(margin: EdgeInsets.only(top: 8.0, bottom: 8.0)),
                  Row(
                    children: <Widget>[
                      Icon(
                        Icons.favorite,
                        color: Colors.red,
                      ),
                      Container(
                        margin: EdgeInsets.only(left: 1.0, right: 1.0),
                      ),
                      Text(
                        voteAverage,
                        style: TextStyle(
                          fontSize: 18.0,
                        ),
                      ),
                      Container(
                        margin: EdgeInsets.only(left: 10.0, right: 50.0),
                      ),
                      Text(
                        "上映日期:${releaseDate}",
                        style: TextStyle(
                          fontSize: 16.0,
                        ),
                      ),
                    ],
                  ),
                  Container(margin: EdgeInsets.only(top: 8.0, bottom: 8.0)),
                  isOverviewSelected
                      ? GestureDetector(
                          onTap: () => setState(() {
                            isOverviewSelected = !isOverviewSelected;
                          }),
                          child: Text(overview),
                        )
                      : GestureDetector(
                          onTap: () => setState(() {
                            isOverviewSelected = !isOverviewSelected;
                          }),
                          child: Column(
                            children: <Widget>[
                              ConstrainedBox(
                                constraints: BoxConstraints(maxHeight: 200),
                                child: Text(
                                  overview,
                                  softWrap: true,
                                  overflow: TextOverflow.visible,
                                  maxLines: 2,
                                ),
                              ),
                              Row(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: <Widget>[
                                  Icon(Icons.arrow_drop_down),
                                  Text('閱讀全文'),
                                ],
                              )
                            ],
                          ),
                        ),
                  Container(margin: EdgeInsets.only(top: 8.0, bottom: 8.0)),
                  Text(
                    "Trailer",
                    style: TextStyle(
                      fontSize: 28.0,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  Container(margin: EdgeInsets.only(top: 8.0, bottom: 8.0)),
                  BlocBuilder(
                    bloc: _youtubeBloc,
                    builder: (context, state) {
                      if (state is YoutubeSuccessState) {
                        return trailerWidget(state.ytResult);
                      }
                      return Center(child: CircularProgressIndicator(),);
                    },
                  ),
                ],
              ),
            )
          ])),
    );
  }
}

稍微介紹一下SliverListSliverAppBar

A sliver is a portion of a scrollable area. You can use slivers to achieve custom scrolling effects.

以上是Flutter官方對Sliver的解釋,非常清楚Sliver代表可以「滑動」的區域。

平常使用的ListView或是GridView也是由Sliver所實做出來的。

使用Sliver的好處是可以創造出更豐富的滑動效果,或是我們這邊要的——讓AppBar能自動隱藏。
想知道如何使用或是更多實際的效果可以看這篇medium

Trailer Widget

在home資料夾下新增「trailer_widget.dart」
創造一個能把YoutubeBloc回傳的影片資訊顯示出來的GridView。

程式碼:

import 'package:flutter/material.dart';
import 'package:youtube_api/youtube_api.dart';
import '../youtube/youtube_player_dialog.dart';

Widget trailerWidget(List<YT_API> videos) {
  if (videos.length > 0) {
    return GridView.builder(
      physics: NeverScrollableScrollPhysics(),
      shrinkWrap: true,
      itemCount: videos.length > 4 ? 4 : videos.length,
      gridDelegate:
      SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
      itemBuilder: (context, index) {
        return Column(
          children: <Widget>[
            GestureDetector(
              child: Image.network(videos[index].thumbnail['default']['url']),
              onTap: () => showDialog(
                  context: context,
                  builder: (context) => YoutubePlayerDialog(
                    videoUrl: videos[index].id,
                  )),
            ),
            ConstrainedBox(
              constraints: BoxConstraints(maxWidth: 130),
              child: Text(
                videos[index].title,
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
              ),
            )
          ],
        );
      },
    );
  } else {
    return Center(
      child: Text(
        '找尋不到影片...',
        style: TextStyle(fontSize: 30, fontWeight: FontWeight.w900),
      ),
    );
  }
}

shrinkWrap一定要設定為true,不然GridView會被無限拉大。
itemCount可以依照你的需求調整要顯示的影片數量。

Youtube Player Dialog

在youtube資料夾新增「youtube_player_dialog.dart」,當使用者點擊影片縮圖就彈出一個Youtube的影片撥放器播放預告片。

程式碼很簡單,使用上次載好的Youtube_Player套件給它影片的id就可以了。

import 'package:youtube_player_flutter/youtube_player_flutter.dart';
import 'package:flutter/material.dart';

class YoutubePlayerDialog extends StatefulWidget {
  final String _videoUrl;
  YoutubePlayerDialog({Key key, String videoUrl})
      : _videoUrl = videoUrl,
        super(key: key);

  @override
  _YoutubePlayerDialogState createState() => _YoutubePlayerDialogState();
}

class _YoutubePlayerDialogState extends State<YoutubePlayerDialog> {
  String get videoUrl => widget._videoUrl;

  @override
  Widget build(BuildContext context) {
    return Dialog(
      child: YoutubePlayer(
        context: context,
        videoId: videoUrl,
        flags: YoutubePlayerFlags(
          autoPlay: true,
          showVideoProgressIndicator: true,
        ),
      ),
    );
  }
}

修改ShowMovieWidget

在Android跳轉頁面是用Intent而在Flutter是使用Navigator.push

開啟show_movie_widget.dart增加點擊跳轉頁面的程式碼。

GestureDetector(
        onTap: () =>Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (context) => MovieDetailPage(
                            posterPath: movieList
                                .results[currentPage.round()].backdropPath,
                            overview:
                                movieList.results[currentPage.round()].overview,
                            title: movieList.results[currentPage.round()].title,
                            releaseDate: movieList
                                .results[currentPage.round()].releaseDate,
                            voteAverage: movieList
                                .results[currentPage.round()].voteAverage,
                            movieId: movieList.results[currentPage.round()].id,
                          ))),
        child: Padding(padding: EdgeInsets.only(top: 10.0), 
		child: Text(
                '點擊看更多細節與預告片',
                style: TextStyle(
                    color: Colors.amberAccent,
                    fontSize: 14.0,
                    fontWeight: FontWeight.bold),
        ),)
),

效果展示

今日總結

今天把電影細節頁面的排版完成、也把播放預告片的功能做完了。比較可惜的是影片只有單純地用關鍵字在Youtube搜尋,可能會找到同名的影片甚至完全無關的,這部分可能就要額外處理了。

我想明天就多增加一個留言功能讓大家能夠討論電影心得,為了能快速開發就使用Firebase提供的Cloud Firestore作為我們的資料庫吧。

完整程式碼在這裡-> FlutTube Github


上一篇
【Flutter基礎概念與實作】 Day23–實作Youtube Bloc、Youtube API
下一篇
【Flutter基礎概念與實作】 Day25–使用Firestore快速建造簡易留言區
系列文
Flutter---Google推出的跨平台框架,Android、iOS一起搞定30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言